Parsed with column specification:
cols(
  ID_REF = col_character(),
  KO1 = col_double(),
  KO2 = col_double(),
  KO3 = col_double(),
  WT1 = col_double(),
  WT2 = col_double(),
  WT3 = col_double()
)

Objectives

The Central Dogma of Biology

DNA makes RNA and RNA makes protein

Cleft Lip and Palate 1/3

Cleft lip and cleft palate (CLP) are splits in the upper lip, the roof of the mouth (palate) or both. They result when facial structures that are developing in an unborn baby do not close completely. CLP is one of the most common birth defects with a frequency of 1/700 live births.

Cleft lip and palate

Cleft Lip and Palate 2/3

Children with cleft lip with or without cleft palate face a variety of challenges, depending on the type and severity of the cleft.

Reference: Mayo Foundation for Medical Education and Research

Cleft Lip and Palate 3/3

Reference: Hum Mol Genet. 2014 May 15; 23(10): 2711–2720

Question

Given:

  1. The pathogenic variant in IRF6 exists in only 70% of the VWS families

  2. IRF6 is a transcription factor

How can we identify other genes that might be involved in the remaining 30% of the VWS families?

Hint

Hypothesis

Why Microarray?

Why Microarray?

Original Paper

PMID: 17041601

Experimental Design

Dataset

Loading

First, we are going to load the dataset from the .tsv file into R as a variable called data using the read.table function.
data is just an arbitrary varilable name to hold the result of read.table and it can be called/named almost anything.

# Load the data from a file into a variable
data = read.table("https://raw.githubusercontent.com/ahmedmoustafa/AUCBIOT5206/master/microarray/datasets/irf6.tsv", header = TRUE, row.names = 1)

# Convert the data.frame (table) in a matrix (numeric)
data = as.matrix(data)

Note: the hash sign (#) indicates that what comes after is a comment. Comments are for documentation and readability of the R code and they are not evaluated (or executed).

Checking

dim(data) # Dimension of the dataset
[1] 45101     6
head(data) # First few rows
                 KO1     KO2     KO3     WT1     WT2     WT3
1415670_at    6531.0  5562.8  6822.4  7732.1  7191.2  7551.9
1415671_at   11486.3 10542.7 10641.4 10408.2  9484.5  7650.2
1415672_at   14339.2 13526.1 14444.7 12936.6 13841.7 13285.7
1415673_at    3156.8  2219.5  3264.4  2374.2  2201.8  2525.3
1415674_a_at  4002.0  3306.9  3777.0  3760.6  3137.0  2911.5
1415675_at    3468.4  3347.4  3332.9  3073.5  3046.0  2914.4

Number of Genes and IDs

number_of_genes = nrow(data) # number of genes = number of rows
number_of_genes
[1] 45101
ids = row.names(data) # The ids of the genes are the names of the rows
head(ids)
[1] "1415670_at"   "1415671_at"   "1415672_at"   "1415673_at"   "1415674_a_at" "1415675_at"  

Exploring

Check the behavior of the data (e.g., normal?, skewed?)

hist(data, col = "gray", main="Histogram")

Transforming

\(log_2\) transformation (why?)

data2 = log2(data)
hist(data2, col = "gray")

Boxplot

colors = c(rep("navy", 3), rep("orange", 3))
boxplot(data2, col = colors)

Clustering 1/2

Hierarchical clustering of the samples (i.e., columns) based on the correlation coefficients of the expression values

hc = hclust(as.dist(1 - cor(data2)))
plot(hc)

Clustering 2/2

To learn more about a function (e.g., hclust), you may type ?function (e.g., ?hclust) in the console to launch R documentation on that function:

Splitting Data Matrix into Two 1/2

ko = data2[, 1:3] # KO matrix
head(ko)
                  KO1      KO2      KO3
1415670_at   12.67309 12.44160 12.73606
1415671_at   13.48763 13.36396 13.37740
1415672_at   13.80768 13.72346 13.81825
1415673_at   11.62425 11.11602 11.67260
1415674_a_at 11.96651 11.69126 11.88303
1415675_at   11.76005 11.70883 11.70256

Splitting Data Matrix into Two 2/2

wt = data2[, 4:6] # WT matrix
head(wt)
                  WT1      WT2      WT3
1415670_at   12.91664 12.81202 12.88262
1415671_at   13.34543 13.21136 12.90128
1415672_at   13.65917 13.75673 13.69759
1415673_at   11.21323 11.10447 11.30224
1415674_a_at 11.87675 11.61517 11.50755
1415675_at   11.58567 11.57270 11.50898

Gene (Row) Mean Expression

# Compute the means of the KO samples
ko.means = rowMeans(ko)
head(ko.means)
  1415670_at   1415671_at   1415672_at   1415673_at 1415674_a_at   1415675_at 
    12.61692     13.40966     13.78313     11.47096     11.84693     11.72381 
# Compute the means of the WT samples
wt.means = rowMeans(wt)
head(wt.means)
  1415670_at   1415671_at   1415672_at   1415673_at 1415674_a_at   1415675_at 
    12.87043     13.15269     13.70450     11.20664     11.66649     11.55578 

Scatter 1/2

plot(ko.means ~ wt.means) # The actual scatter plot
abline(0, 1, col = "red") # Only a diagonal line

Scatter 2/2

pairs(data2) # All pairwise comparisons

Differentially Expressed Genes (DEGs)

To identify DEGs, we will identify:

Then, we will take the overlap (intersection) of the two sets

Biological Significance (fold-change) 1/2

fold = ko.means - wt.means # Difference between means
head(fold)
  1415670_at   1415671_at   1415672_at   1415673_at 1415674_a_at   1415675_at 
 -0.25351267   0.25697097   0.07863227   0.26431191   0.18044345   0.16803065 

Biological Significance (fold-change) 2/2

hist(fold, col = "gray") # Histogram of the fold

Statistical Significance (p-value) 1/3

t-test

Let’s say there are two samples x and y from the two populations, X and Y, respectively, to determine whether the means of two populations are significantly different, we can use t.test.

?t.test

t-test : Example 1

x = c(4, 3, 10, 7, 9) ; y = c(7, 4, 3, 8, 10)
t.test(x, y)

    Welch Two Sample t-test

data:  x and y
t = 0.1066, df = 7.9743, p-value = 0.9177
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 -4.12888  4.52888
sample estimates:
mean of x mean of y 
      6.6       6.4 
t.test(x, y)$p.value
[1] 0.917739

t-test : Example 2

x = c(6, 8, 10, 7, 9) ; y = c(3, 2, 1, 4, 5)
t.test(x, y)

    Welch Two Sample t-test

data:  x and y
t = 5, df = 8, p-value = 0.001053
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 2.693996 7.306004
sample estimates:
mean of x mean of y 
        8         3 
t.test(x, y)$p.value
[1] 0.001052826

Statistical Significance (p-value) 2/3

Let’s compute the p-value for all genes using a for-loop of t.test, one gene at a time:

pvalue = NULL # Empty list for the p-values

for(i in 1 : number_of_genes) { # for each gene from to the number of genes
  x = wt[i, ] # wt values of gene number i
  y = ko[i, ] # ko values of gene number i
  t = t.test(x, y) # t-test between the two conditions
  pvalue[i] = t$p.value # Store p-value number i into the list of p-values
}
head(pvalue)
[1] 0.092706280 0.182663337 0.129779075 0.272899180 0.262377176 0.005947807

Statistical Significance (p-value) 3/3

hist(-log10(pvalue), col = "gray") # Histogram of p-values (-log10)

Volcano : Statistical & Biological 1/3

plot(-log10(pvalue) ~ fold)

Volcano : Statistical & Biological 2/3

fold_cutoff = 2
pvalue_cutoff = 0.01

plot(-log10(pvalue) ~ fold)

abline(v = fold_cutoff, col = "blue", lwd = 3)
abline(v = -fold_cutoff, col = "red", lwd = 3)
abline(h = -log10(pvalue_cutoff), col = "green", lwd = 3)

Volcano : Statistical & Biological 3/3

Filtering for DEGs 1/3

filter_by_fold = abs(fold) >= fold_cutoff # Biological
sum(filter_by_fold) # Number of genes staisfy the condition
[1] 1051
filter_by_pvalue = pvalue <= pvalue_cutoff # Statistical
sum(filter_by_pvalue)
[1] 1564
filter_combined = filter_by_fold & filter_by_pvalue # Combined
sum(filter_combined)
[1] 276

Filtering for DEGs 2/3

filtered = data2[filter_combined, ]
dim(filtered)
[1] 276   6
head(filtered)
                   KO1       KO2       KO3      WT1       WT2       WT3
1416200_at   13.312004 12.973357 12.868456  7.40429  8.558803  8.683696
1416236_a_at 14.148397 14.039236 14.130007 12.23604 12.022402 11.495056
1417808_at    5.321928  5.442943  4.053111 15.16978 15.070087 14.753274
1417932_at   10.602884 10.257152 10.496055 13.98445 14.203294 13.720960
1418050_at   10.622052 10.975490 10.795066 12.86513 13.012048 12.658122
1418100_at    9.117903  8.634811  9.057721 12.90358 12.842449 12.233769

Filtering for DEGs 3/3

plot(-log10(pvalue) ~ fold)
points(-log10(pvalue[filter_combined]) ~ fold[filter_combined],
       col = "green")

Exercise

On the volcano plot, highlight the up-regulated genes in red and the download-regulated genes in blue

Solution 1/2

# Screen for the up-regulated genes (+ve fold)
filter_up = filter_combined & fold > 0

head(filter_up)
  1415670_at   1415671_at   1415672_at   1415673_at 1415674_a_at   1415675_at 
       FALSE        FALSE        FALSE        FALSE        FALSE        FALSE 
# Number of filtered genes
sum(filter_up)
[1] 95
# Screen for the down-regulated genes (-ve fold)
filter_down = filter_combined & fold < 0

head(filter_down)
  1415670_at   1415671_at   1415672_at   1415673_at 1415674_a_at   1415675_at 
       FALSE        FALSE        FALSE        FALSE        FALSE        FALSE 
# Number of filtered genes
sum(filter_down)
[1] 181

Solution 2/2

plot(-log10(pvalue) ~ fold)
points(-log10(pvalue[filter_up]) ~ fold[filter_up], col = "red")
points(-log10(pvalue[filter_down]) ~ fold[filter_down], col = "blue")

Heatmap 1/5

heatmap(filtered)

Heatmap 2/5

Heatmap 3/5

# Clustering of the columns (samples)
col_dendrogram = as.dendrogram(hclust(as.dist(1-cor(filtered))))

# Clustering of the rows (genes)
row_dendrogram = as.dendrogram(hclust(as.dist(1-cor(t(filtered)))))

Heatmap 4/5

# Heatmap with the rows and columns clustered by correlation coefficients
heatmap(filtered, Rowv=row_dendrogram, Colv=col_dendrogram)

Heatmap 5/5

library(gplots) # Load the gplots library
heatmap(filtered, Rowv=row_dendrogram, Colv=col_dendrogram, col = rev(redgreen(1024)))

Annnotation 1/3

To obtain the functional annotation of the differentially expressed genes, we are going first to extract their probe ids:

filterd_ids = row.names(filtered) # ids of the filtered DE genes
length(filterd_ids)
[1] 276
head(filterd_ids)
[1] "1416200_at"   "1416236_a_at" "1417808_at"   "1417932_at"   "1418050_at"   "1418100_at"  

Sanity Check (Irf6)

Down Regulation of Irf6

Multiple Testing Correction 1/3

We conducted 45101 statistical tests. The computed p-values need to be corrected for multiple testing. The correction can be performed using p.adjust, which simply takes the orignial p-values a vector and returns the adjusted (corrected) p-values:

adjusted.pvalues = p.adjust(pvalue, method = "fdr")

Number original p-values \(\leq\) 0.05 = 5099 while the number adjusted (corrected) p-values < 0.05 \(\geq\) 9

Multiple Testing Correction 2/3

Here is an example of the original p-values and corresponding adjusted p-values:

Multiple Testing Correction 3/3

Homework

LS0tCnRpdGxlOiAnTWljcm9hcnJheSBHZW5lIEV4cHJlc3Npb24gQW5hbHlzaXMgd2l0aCBSJwpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6IAogICAgdG9jOiB5ZXMKc3VidGl0bGU6ICdFeGFtcGxlOiBJbnRlcmZlcm9uIFJlZ3VsYXRvcnkgRmFjdG9yIDYgKCpJUkY2KiknCmxpY2Vuc2U6IGJ5LXNhCi0tLQoKYGBge3IgbGlicmFyaWVzLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KHByaW50cikKbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGNvd3Bsb3QpCmBgYAoKIyMgT2JqZWN0aXZlcwotIExvYWQgbWljcm9hcnJheSBkYXRhc2V0IGludG8gUgotIEV4cGxvcmUgdGhlIGRhdGFzZXQgd2l0aCBiYXNpYyB2aXN1YWxpemF0aW9ucwotIElkZW50aWZ5IGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcyAoREVHcykKLSBHZW5lcmF0ZSBhbm5vdGF0aW9uIG9mIHRoZSBERUdzICgqVGVudGF0aXZlKikKCjxjZW50ZXI+CiFbXShpbWFnZXMvdGl0bGUucG5nICJNaWNyb2FycnkgQW5hbHlzaXMgd2l0aCBSIikKPC9jZW50ZXI+CgojIyBUaGUgQ2VudHJhbCBEb2dtYSBvZiBCaW9sb2d5CiFbRE5BIG1ha2VzIFJOQSBhbmQgUk5BIG1ha2VzIHByb3RlaW5dKGltYWdlcy9kb2dtYS5wbmcgIlRoZSBDZW50cmFsIERvZ21hIG9mIEJpb2xvZ3kiKQoKCiMjIENsZWZ0IExpcCBhbmQgUGFsYXRlIDEvMwoKQ2xlZnQgbGlwIGFuZCBjbGVmdCBwYWxhdGUgKCoqQ0xQKiopIGFyZSBzcGxpdHMgaW4gdGhlIHVwcGVyIGxpcCwgdGhlIHJvb2Ygb2YgdGhlIG1vdXRoIChwYWxhdGUpIG9yIGJvdGguIFRoZXkgcmVzdWx0IHdoZW4gZmFjaWFsIHN0cnVjdHVyZXMgdGhhdCBhcmUgZGV2ZWxvcGluZyBpbiBhbiB1bmJvcm4gYmFieSBkbyBub3QgY2xvc2UgY29tcGxldGVseS4gQ0xQIGlzIG9uZSBvZiB0aGUgbW9zdCBjb21tb24gYmlydGggZGVmZWN0cyB3aXRoIGEgZnJlcXVlbmN5IG9mIDEvNzAwIGxpdmUgYmlydGhzLgoKIVtDbGVmdCBsaXAgYW5kIHBhbGF0ZV0oaW1hZ2VzL2NsZWZ0LmpwZykKCiMjIENsZWZ0IExpcCBhbmQgUGFsYXRlIDIvMwoKQ2hpbGRyZW4gd2l0aCBjbGVmdCBsaXAgd2l0aCBvciB3aXRob3V0IGNsZWZ0IHBhbGF0ZSBmYWNlIGEgdmFyaWV0eSBvZiBjaGFsbGVuZ2VzLCBkZXBlbmRpbmcgb24gdGhlIHR5cGUgYW5kIHNldmVyaXR5IG9mIHRoZSBjbGVmdC4KCi0gKipEaWZmaWN1bHR5IGZlZWRpbmcuKiogT25lIG9mIHRoZSBtb3N0IGltbWVkaWF0ZSBjb25jZXJucyBhZnRlciBiaXJ0aCBpcyBmZWVkaW5nLgoKLSAqKkVhciBpbmZlY3Rpb25zIGFuZCBoZWFyaW5nIGxvc3MuKiogQmFiaWVzIHdpdGggY2xlZnQgcGFsYXRlIGFyZSBlc3BlY2lhbGx5IGF0IHJpc2sgb2YgZGV2ZWxvcGluZyBtaWRkbGUgZWFyIGZsdWlkIGFuZCBoZWFyaW5nIGxvc3MuCgotICoqRGVudGFsIHByb2JsZW1zLioqIElmIHRoZSBjbGVmdCBleHRlbmRzIHRocm91Z2ggdGhlIHVwcGVyIGd1bSwgdG9vdGggZGV2ZWxvcG1lbnQgbWF5IGJlIGFmZmVjdGVkLgoKLSAqKlNwZWVjaCBkaWZmaWN1bHRpZXMuKiogQmVjYXVzZSB0aGUgcGFsYXRlIGlzIHVzZWQgaW4gZm9ybWluZyBzb3VuZHMsIHRoZSBkZXZlbG9wbWVudCBvZiBub3JtYWwgc3BlZWNoIGNhbiBiZSBhZmZlY3RlZCBieSBhIGNsZWZ0IHBhbGF0ZS4gU3BlZWNoIG1heSBzb3VuZCB0b28gbmFzYWwuCgoqUmVmZXJlbmNlKjogW01heW8gRm91bmRhdGlvbiBmb3IgTWVkaWNhbCBFZHVjYXRpb24gYW5kIFJlc2VhcmNoXShodHRwczovL3d3dy5tYXlvY2xpbmljLm9yZy9kaXNlYXNlcy1jb25kaXRpb25zL2NsZWZ0LXBhbGF0ZS9zeW1wdG9tcy1jYXVzZXMvc3ljLTIwMzcwOTg1KQoKIyMgQ2xlZnQgTGlwIGFuZCBQYWxhdGUgMy8zCgotIEROQSB2YXJpYXRpb24gaW4gSW50ZXJmZXJvbiBSZWd1bGF0b3J5IEZhY3RvciA2ICgqKklSRjYqKikgY2F1c2VzIFZhbiBkZXIgV291ZGUgc3luZHJvbWUgKCoqVldTKiopCgotIFZXUyBpcyB0aGUgbW9zdCBjb21tb24gc3luZHJvbWljIGZvcm0gb2YgY2xlZnQgbGlwIGFuZCBwYWxhdGUuCgotIEhvd2V2ZXIsIHRoZSBjYXVzaW5nIHZhcmlhbnQgaW4gSVJGNiBoYXMgYmVlbiBmb3VuZCBpbiAqb25seSogNzAlIG9mIFZXUyBmYW1pbGllcyEKCi0gSVJGNiBpcyBhICoqdHJhbnNjcmlwdGlvbiBmYWN0b3IqKiB3aXRoIGEgY29uc2VydmVkIGhlbGl4LWxvb3AtaGVsaXggRE5BIGJpbmRpbmcgZG9tYWluIGFuZCBhIGxlc3Mgd2VsbC1jb25zZXJ2ZWQgcHJvdGVpbiBiaW5kaW5nIGRvbWFpbi4gCgoqUmVmZXJlbmNlKjogW0h1bSBNb2wgR2VuZXQuIDIwMTQgTWF5IDE1OyAyMygxMCk6IDI3MTHigJMyNzIwXShodHRwOi8vZG9pLm9yZy8xMC4xMDkzL2htZy9kZHQ2NjQpCgojIyBRdWVzdGlvbgoKR2l2ZW46CgoxLiBUaGUgcGF0aG9nZW5pYyB2YXJpYW50IGluIElSRjYgZXhpc3RzIGluIG9ubHkgNzAlIG9mIHRoZSBWV1MgZmFtaWxpZXMKCjIuIElSRjYgaXMgYSB0cmFuc2NyaXB0aW9uIGZhY3RvcgoKSG93IGNhbiB3ZSBpZGVudGlmeSBvdGhlciBnZW5lcyB0aGF0IG1pZ2h0IGJlIGludm9sdmVkIGluIHRoZSByZW1haW5pbmcgMzAlIG9mIHRoZSBWV1MgZmFtaWxpZXM/CgojIyBIaW50CgotIFVzdWFsbHksIGdlbmVzIHRoYXQgYXJlIHJlZ3VsYXRlZCBieSBhIHRyYW5zY3JpcHRpb24gZmFjdG9yIGJlbG9uZyB0byB0aGUgc2FtZSBiaW9sb2dpY2FsIHByb2Nlc3Mgb3IgcGF0aHdheS4KCi0gVGhlcmVmb3JlLCBieSBjb21wYXJpbmcgdGhlIGdlbmUgZXhwcmVzc2lvbiBwYXR0ZXJucyBiZXR3ZWVuIHdpbGQtdHlwZSAoZnVuY3Rpb25hbCkgKklyZjYqIGFuZCBrbm9ja291dCAobm9uLWZ1bmN0aW9uYWwpICpJcmY2KiwgaXQgY291bGQgYmUgcG9zc2libGUgdG8gaWRlbnRpZnkgZ2VuZXMgdGhhdCBhcmUgcmVndWxhdGVkICh0YXJnZXRlZCkgYnkgKklyZjYqLgoKIyMgSHlwb3RoZXNpcwoKLSBcKEhfTyA6IFxtdV97V1R9ID0gXG11X3tLT31cKQoKLSBcKEhfQSA6IFxtdV97V1R9IFxuZSBcbXVfe0tPfVwpCgotIFdoZXJlIFwoXG11XCkgaXMgdGhlICptZWFuKiBvZiB0aGUgZ2VuZSBleHByZXNzaW9uIHZhbHVlcyBvZiBhIGdlbmUuCgotICoqT25lKiotc2lkZWQgb3IgKipUd28qKi1zaWRlZCB0ZXN0aW5nPwoKYGBge3Igc2lkZXMsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGZpZy5oZWlnaHQ9Mn0KbiA9IDFlNgpjdXRvZmYgPSBxbm9ybSgwLjA1KQpteWRhdGEgPSBkYXRhLmZyYW1lKHggPSBybm9ybShuKSkKbGVmdCA9IG15ZGF0YSAlPiUgZmlsdGVyKHggPCBjdXRvZmYpCnJpZ2h0ID0gbXlkYXRhICU+JSBmaWx0ZXIoeCA+IC1jdXRvZmYpCmJvdGggPSBteWRhdGEgJT4lIGZpbHRlcih4IDwgY3V0b2ZmIHwgeCA+IC1jdXRvZmYpCgpiaW53aWR0aCA9IDFlLTEKCnAgPSBnZ3Bsb3QoKQpwID0gcCArIGdlb21faGlzdG9ncmFtKGRhdGEgPSBteWRhdGEsIGFlcyh4ID0geCksIGZpbGwgPSAiZ3JheSIsIGJpbndpZHRoID0gYmlud2lkdGgpCnAgPSBwICsgZ2VvbV9oaXN0b2dyYW0oZGF0YSA9IGxlZnQsIGFlcyh4ID0geCksIGZpbGwgPSAicmVkIiwgYmlud2lkdGggPSBiaW53aWR0aCkKcCA9IHAgKyBsYWJzKHggPSAiIiwgeSA9ICIiKQpwID0gcCArIHRoZW1lX2xpZ2h0KCkKcCA9IHAgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkKcCA9IHAgKyB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSkKcCA9IHAgKyB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKQpwMSA9IHAKCnAgPSBnZ3Bsb3QoKQpwID0gcCArIGdlb21faGlzdG9ncmFtKGRhdGEgPSBteWRhdGEsIGFlcyh4ID0geCksIGZpbGwgPSAiZ3JheSIsIGJpbndpZHRoID0gYmlud2lkdGgpCnAgPSBwICsgZ2VvbV9oaXN0b2dyYW0oZGF0YSA9IHJpZ2h0LCBhZXMoeCA9IHgpLCBmaWxsID0gImJsdWUiLCBiaW53aWR0aCA9IGJpbndpZHRoKQpwID0gcCArIGxhYnMoeCA9ICIiLCB5ID0gIiIpCnAgPSBwICsgdGhlbWVfbGlnaHQoKQpwID0gcCArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKQpwID0gcCArIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpKQpwID0gcCArIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCkpCnAyID0gcAoKcCA9IGdncGxvdCgpCnAgPSBwICsgZ2VvbV9oaXN0b2dyYW0oZGF0YSA9IG15ZGF0YSwgYWVzKHggPSB4KSwgZmlsbCA9ICJncmF5IiwgYmlud2lkdGggPSBiaW53aWR0aCkKcCA9IHAgKyBnZW9tX2hpc3RvZ3JhbShkYXRhID0gbGVmdCwgYWVzKHggPSB4KSwgZmlsbCA9ICJyZWQiLCBiaW53aWR0aCA9IGJpbndpZHRoKQpwID0gcCArIGdlb21faGlzdG9ncmFtKGRhdGEgPSByaWdodCwgYWVzKHggPSB4KSwgZmlsbCA9ICJibHVlIiwgYmlud2lkdGggPSBiaW53aWR0aCkKcCA9IHAgKyBsYWJzKHggPSAiIiwgeSA9ICIiKQpwID0gcCArIHRoZW1lX2xpZ2h0KCkKcCA9IHAgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkKcCA9IHAgKyB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSkKcCA9IHAgKyB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKQpwMyA9IHAKCnBsb3RfZ3JpZChwMSwgcDIsIHAzLCBsYWJlbHMgPSBjKCJhIiwgImIiLCAiYyIpLCBuY29sID0gMykKYGBgCgojIyBXaHkgTWljcm9hcnJheT8KCiFbXShpbWFnZXMvb25lLWRvZXMtbm90LXNpbXBseS5qcGcpCgojIyBXaHkgTWljcm9hcnJheT8KCi0gTm8gbmVlZCBmb3IgY2FuZGlkYXRlIGdlbmVzIChvciBnZW5lcyBvZiBpbnRlcmVzdCkKCi0gT25lIGV4cGVyaW1lbnQgYXNzZXNzZXMgdGhlIGVudGlyZSB0cmFuc2NyaXB0b21lCgotIE9uZSBleHBlcmltZW50IGdlbmVyYXRlcyBtYW55IGh5cG90aGVzZXMKCi0gT25seSBzbWFsbCBhbW91bnQgb2YgUk5BIGlzIHJlcXVpcmVkICh+MTXigJMyMDAgbmcpCgohW10oaW1hZ2VzL2NoaXAuanBnKQoKCiMjIE9yaWdpbmFsIFBhcGVyCgohW1BNSUQ6IDE3MDQxNjAxXShpbWFnZXMvcG1pZDE3MDQxNjAxLnBuZykKCiMjIEV4cGVyaW1lbnRhbCBEZXNpZ24KCi0gMyBJUkY2IHdpbGQtdHlwZSAoKy8rKSBhbmQgMyBrbm9ja291dCAoLS8tKSBtb3VzZSBlbWJyeW9zLgotIEUxNy41IGVtYnJ5b3Mgd2VyZSByZW1vdmVkIGZyb20gZXV0aGFuaXplZCBtb3RoZXJzLgotIFNraW4gd2FzIHJlbW92ZWQgZnJvbSBlbWJyeW9zLgotIFRvdGFsIFJOQSB3YXMgaXNvbGF0ZWQgZnJvbSB0aGUgc2tpbi4KLSBSZXN1bHRhbnQgUk5BIHdhcyBoeWJyaWRpemVkIHRvIEFmZnltZXRyaXggR2VuZUNoaXAgTW91c2UgR2Vub21lIDQzMCAyLjAgYXJyYXlzLgoKIVtdKGltYWdlcy9taWNlLnBuZykKCiMjIERhdGFzZXQKCi0gVGhlIG9yaWdpbmFsIGRhdGFzZXQgY2FuIGJlIG9idGFpbmVkIGZyb20gTkNCSSBHRU8gd2l0aCBhY2Nlc3Npb24gW0dTRTU4MDBdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvZ2VvL3F1ZXJ5L2FjYy5jZ2k/YWNjPUdTRTU4MDApCgpgYGB7ciBkYXRhc2V0LCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpkZiA9IHJlYWRfdHN2KCJkYXRhL0lyZjYudHN2IilbMTo0LF0KaGVhZChkZikKYGBgCgoKIyMgTG9hZGluZwpGaXJzdCwgd2UgYXJlIGdvaW5nIHRvIGxvYWQgdGhlIGRhdGFzZXQgZnJvbSB0aGUgYC50c3ZgIGZpbGUgaW50byBgUmAgYXMgYSB2YXJpYWJsZSBjYWxsZWQgYGRhdGFgIHVzaW5nIHRoZSBbYHJlYWQudGFibGVgXShodHRwOi8vd3d3Lmluc2lkZS1yLm9yZy9yLWRvYy91dGlscy9yZWFkLnRhYmxlKSBmdW5jdGlvbi4KPGJyPgpgZGF0YWAgaXMganVzdCBhbiBhcmJpdHJhcnkgKip2YXJpbGFibGUqKiBuYW1lIHRvIGhvbGQgdGhlIHJlc3VsdCBvZiBgcmVhZC50YWJsZWAgYW5kIGl0IGNhbiBiZSBjYWxsZWQvbmFtZWQgKmFsbW9zdCogYW55dGhpbmcuCgpgYGB7cn0KIyBMb2FkIHRoZSBkYXRhIGZyb20gYSBmaWxlIGludG8gYSB2YXJpYWJsZQpkYXRhID0gcmVhZC50YWJsZSgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2FobWVkbW91c3RhZmEvQVVDQklPVDUyMDYvbWFzdGVyL21pY3JvYXJyYXkvZGF0YXNldHMvaXJmNi50c3YiLCBoZWFkZXIgPSBUUlVFLCByb3cubmFtZXMgPSAxKQoKIyBDb252ZXJ0IHRoZSBkYXRhLmZyYW1lICh0YWJsZSkgaW4gYSBtYXRyaXggKG51bWVyaWMpCmRhdGEgPSBhcy5tYXRyaXgoZGF0YSkKYGBgCgoqKk5vdGU6KiogdGhlIGhhc2ggc2lnbiAoYCNgKSBpbmRpY2F0ZXMgdGhhdCB3aGF0IGNvbWVzIGFmdGVyIGlzIGEgKmNvbW1lbnQqLiBDb21tZW50cyBhcmUgZm9yIGRvY3VtZW50YXRpb24gYW5kIHJlYWRhYmlsaXR5IG9mIHRoZSBgUmAgY29kZSBhbmQgdGhleSBhcmUgbm90IGV2YWx1YXRlZCAob3IgZXhlY3V0ZWQpLgoKIyMgQ2hlY2tpbmcKCmBgYHtyfQpkaW0oZGF0YSkgIyBEaW1lbnNpb24gb2YgdGhlIGRhdGFzZXQKaGVhZChkYXRhKSAjIEZpcnN0IGZldyByb3dzCmBgYAoKIyMgTnVtYmVyIG9mIEdlbmVzIGFuZCBJRHMKYGBge3J9Cm51bWJlcl9vZl9nZW5lcyA9IG5yb3coZGF0YSkgIyBudW1iZXIgb2YgZ2VuZXMgPSBudW1iZXIgb2Ygcm93cwpudW1iZXJfb2ZfZ2VuZXMKCmlkcyA9IHJvdy5uYW1lcyhkYXRhKSAjIFRoZSBpZHMgb2YgdGhlIGdlbmVzIGFyZSB0aGUgbmFtZXMgb2YgdGhlIHJvd3MKaGVhZChpZHMpCmBgYAoKIyMgRXhwbG9yaW5nCkNoZWNrIHRoZSBiZWhhdmlvciBvZiB0aGUgZGF0YSAoZS5nLiwgbm9ybWFsPywgc2tld2VkPykKCmBgYHtyfQpoaXN0KGRhdGEsIGNvbCA9ICJncmF5IiwgbWFpbj0iSGlzdG9ncmFtIikKYGBgCgojIyBUcmFuc2Zvcm1pbmcKClwobG9nXzJcKSB0cmFuc2Zvcm1hdGlvbiAod2h5PykKCmBgYHtyfQpkYXRhMiA9IGxvZzIoZGF0YSkKaGlzdChkYXRhMiwgY29sID0gImdyYXkiKQpgYGAKCiMjIEJveHBsb3QKCmBgYHtyfQpjb2xvcnMgPSBjKHJlcCgibmF2eSIsIDMpLCByZXAoIm9yYW5nZSIsIDMpKQpib3hwbG90KGRhdGEyLCBjb2wgPSBjb2xvcnMpCmBgYAoKIyMgQ2x1c3RlcmluZyAxLzIKCkhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG9mIHRoZSAqKnNhbXBsZXMqKiAoaS5lLiwgY29sdW1ucykgYmFzZWQgb24gdGhlIFtjb3JyZWxhdGlvbiBjb2VmZmljaWVudHNdKGh0dHA6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvUGVhcnNvbl9wcm9kdWN0LW1vbWVudF9jb3JyZWxhdGlvbl9jb2VmZmljaWVudCkgb2YgdGhlIGV4cHJlc3Npb24gdmFsdWVzCgpgYGB7cn0KaGMgPSBoY2x1c3QoYXMuZGlzdCgxIC0gY29yKGRhdGEyKSkpCnBsb3QoaGMpCmBgYAoKIyMgQ2x1c3RlcmluZyAyLzIKVG8gbGVhcm4gbW9yZSBhYm91dCBhIGZ1bmN0aW9uIChlLmcuLCBgaGNsdXN0YCksIHlvdSBtYXkgdHlwZSBgP2Z1bmN0aW9uYCAoZS5nLiwgYD9oY2x1c3RgKSBpbiB0aGUgYGNvbnNvbGVgIHRvIGxhdW5jaCBgUmAgZG9jdW1lbnRhdGlvbiBvbiB0aGF0IGZ1bmN0aW9uOgoKIyMgU3BsaXR0aW5nIERhdGEgTWF0cml4IGludG8gVHdvIDEvMgpgYGB7cn0Ka28gPSBkYXRhMlssIDE6M10gIyBLTyBtYXRyaXgKaGVhZChrbykKYGBgCgojIyBTcGxpdHRpbmcgRGF0YSBNYXRyaXggaW50byBUd28gMi8yCmBgYHtyfQp3dCA9IGRhdGEyWywgNDo2XSAjIFdUIG1hdHJpeApoZWFkKHd0KQpgYGAKCgojIyBHZW5lIChSb3cpIE1lYW4gRXhwcmVzc2lvbgoKYGBge3J9CiMgQ29tcHV0ZSB0aGUgbWVhbnMgb2YgdGhlIEtPIHNhbXBsZXMKa28ubWVhbnMgPSByb3dNZWFucyhrbykKaGVhZChrby5tZWFucykKCiMgQ29tcHV0ZSB0aGUgbWVhbnMgb2YgdGhlIFdUIHNhbXBsZXMKd3QubWVhbnMgPSByb3dNZWFucyh3dCkKaGVhZCh3dC5tZWFucykKCmBgYAoKCiMjIFNjYXR0ZXIgMS8yCmBgYHtyfQpwbG90KGtvLm1lYW5zIH4gd3QubWVhbnMpICMgVGhlIGFjdHVhbCBzY2F0dGVyIHBsb3QKYWJsaW5lKDAsIDEsIGNvbCA9ICJyZWQiKSAjIE9ubHkgYSBkaWFnb25hbCBsaW5lCmBgYAoKIyMgU2NhdHRlciAyLzIKYGBge3J9CnBhaXJzKGRhdGEyKSAjIEFsbCBwYWlyd2lzZSBjb21wYXJpc29ucwpgYGAKCiMjIERpZmZlcmVudGlhbGx5IEV4cHJlc3NlZCBHZW5lcyAoREVHcykKClRvIGlkZW50aWZ5IERFR3MsIHdlIHdpbGwgaWRlbnRpZnk6CgotICoqQmlvbG9naWNhbGx5Kiogc2lnbmlmaWNhbnRseSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQKLSAqKlN0YXRpc3RpY2FsbHkqKiBzaWduaWZpY2FudGx5IGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZAoKVGhlbiwgd2Ugd2lsbCB0YWtlIHRoZSAqKm92ZXJsYXAqKiAoKippbnRlcnNlY3Rpb24qKikgb2YgdGhlIHR3byBzZXRzCgohW10oaW1hZ2VzL2ludGVyc2VjdGlvbi5wbmcpCgojIyBCaW9sb2dpY2FsIFNpZ25pZmljYW5jZSAoZm9sZC1jaGFuZ2UpIDEvMgpgYGB7cn0KZm9sZCA9IGtvLm1lYW5zIC0gd3QubWVhbnMgIyBEaWZmZXJlbmNlIGJldHdlZW4gbWVhbnMKaGVhZChmb2xkKQpgYGAKCi0gV2hhdCBkbyB0aGUgcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIHZhbHVlcyBvZiB0aGUgZm9sZC1jaGFuZ2UgaW5kaWNhdGU/IENvbnNpZGVyaW5nIHRoZSBgV1RgIGNvbmRpdGlvbiBpcyB0aGUgKipyZWZlcmVuY2UqKiAob3IgKipjb250cm9sKiopCgotICoqK3ZlKiogZm9sZC1jaGFuZ2UgXChccmlnaHRhcnJvd1wpICoqVXAqKi1yZWd1bGF0aW9uIFwoXHVwYXJyb3dcKQotICoqLXZlKiogZm9sZC1jaGFuZ2UgXChccmlnaHRhcnJvd1wpICoqRG93bioqLXJlZ3VsYXRpb24gXChcZG93bmFycm93XCkKCiMjIEJpb2xvZ2ljYWwgU2lnbmlmaWNhbmNlIChmb2xkLWNoYW5nZSkgMi8yCmBgYHtyfQpoaXN0KGZvbGQsIGNvbCA9ICJncmF5IikgIyBIaXN0b2dyYW0gb2YgdGhlIGZvbGQKYGBgCgojIyBTdGF0aXN0aWNhbCBTaWduaWZpY2FuY2UgKCpwKi12YWx1ZSkgMS8zCi0gVG8gYXNzZXNzIHRoZSBzdGF0aXN0aWNhbCBzaWduaWZpY2FuY2Ugb2YgdGhlIGRpZmZlcmVuY2UgaW4gdGhlIGV4cHJlc3Npb24gdmFsdWVzIGZvciBlYWNoIGdlbmUgYmV0d2VlbiB0aGUgdHdvIGNvbmRpdGlvbnMgKGUuZy4sIGBXVGAgYW5kIGBLT2ApLCB3ZSBhcmUgZ29pbmcgdG8gdXNlIFsqdCotdGVzdF0oaHR0cDovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9TdHVkZW50JTI3c190LXRlc3QpLgoKYGBge3IgZWNobz1GQUxTRSxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0UsZmlnLmhlaWdodD0zfQpuID0gMWU2CnggPSBkYXRhLmZyYW1lKFZhbHVlID0gcm5vcm0obiwgbSA9IDAuMSwgc2QgPSAxKSwgQ29uZGl0aW9uID0gIlgiKQp5ID0gZGF0YS5mcmFtZShWYWx1ZSA9IHJub3JtKG4sIG0gPSAtMC4xLCBzZCA9IDEpLCBDb25kaXRpb24gPSAiWSIpCnogPSB4ICU+JSBiaW5kX3Jvd3MoeSkKcCA9IGdncGxvdCh6KQpwID0gcCArIGdlb21fZGVuc2l0eShhZXMoeCA9IFZhbHVlLCBmaWxsID0gQ29uZGl0aW9uKSwgYWxwaGEgPSAwLjUpCnAgPSBwICsgdGhlbWVfbGlnaHQoKQpwID0gcCArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQpwMSA9IHAKCnggPSBkYXRhLmZyYW1lKFZhbHVlID0gcm5vcm0obiwgbSA9IDIsIHNkID0gMSksIENvbmRpdGlvbiA9ICJYIikKeSA9IGRhdGEuZnJhbWUoVmFsdWUgPSBybm9ybShuLCBtID0gLTIsIHNkID0gMSksIENvbmRpdGlvbiA9ICJZIikKeiA9IHggJT4lIGJpbmRfcm93cyh5KQpwID0gZ2dwbG90KHopCnAgPSBwICsgZ2VvbV9kZW5zaXR5KGFlcyh4ID0gVmFsdWUsIGZpbGwgPSBDb25kaXRpb24pLCBhbHBoYSA9IDAuNSkKcCA9IHAgKyB0aGVtZV9saWdodCgpCnAgPSBwICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpCnAyID0gcAoKcGxvdF9ncmlkKHAxLCBwMiwgbGFiZWxzID0gYygiYSIsICJiIikpCgpgYGAKCiMjICp0Ki10ZXN0CgpMZXQncyBzYXkgdGhlcmUgYXJlIHR3byBzYW1wbGVzICp4KiBhbmQgKnkqIGZyb20gdGhlIHR3byBwb3B1bGF0aW9ucywgKlgqIGFuZCAqWSosIHJlc3BlY3RpdmVseSwgdG8gZGV0ZXJtaW5lIHdoZXRoZXIgdGhlIG1lYW5zIG9mIHR3byBwb3B1bGF0aW9ucyBhcmUgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQsIHdlIGNhbiB1c2UgYHQudGVzdGAuCgpgYGB7cn0KP3QudGVzdApgYGAKCiMjICp0Ki10ZXN0IDogRXhhbXBsZSAxCgpgYGB7cn0KeCA9IGMoNCwgMywgMTAsIDcsIDkpIDsgeSA9IGMoNywgNCwgMywgOCwgMTApCnQudGVzdCh4LCB5KQpgYGAKCmBgYHtyfQp0LnRlc3QoeCwgeSkkcC52YWx1ZQpgYGAKCiMjICp0Ki10ZXN0IDogRXhhbXBsZSAyCgpgYGB7cn0KeCA9IGMoNiwgOCwgMTAsIDcsIDkpIDsgeSA9IGMoMywgMiwgMSwgNCwgNSkKdC50ZXN0KHgsIHkpCmBgYAoKYGBge3J9CnQudGVzdCh4LCB5KSRwLnZhbHVlCmBgYAoKIyMgU3RhdGlzdGljYWwgU2lnbmlmaWNhbmNlICgqcCotdmFsdWUpIDIvMwoKTGV0J3MgY29tcHV0ZSB0aGUgKnAqLXZhbHVlIGZvciBhbGwgZ2VuZXMgdXNpbmcgYSBgZm9yYC1sb29wIG9mIGB0LnRlc3RgLCBvbmUgZ2VuZSBhdCBhIHRpbWU6CgpgYGB7cn0KcHZhbHVlID0gTlVMTCAjIEVtcHR5IGxpc3QgZm9yIHRoZSBwLXZhbHVlcwoKZm9yKGkgaW4gMSA6IG51bWJlcl9vZl9nZW5lcykgeyAjIGZvciBlYWNoIGdlbmUgZnJvbSB0byB0aGUgbnVtYmVyIG9mIGdlbmVzCiAgeCA9IHd0W2ksIF0gIyB3dCB2YWx1ZXMgb2YgZ2VuZSBudW1iZXIgaQogIHkgPSBrb1tpLCBdICMga28gdmFsdWVzIG9mIGdlbmUgbnVtYmVyIGkKICB0ID0gdC50ZXN0KHgsIHkpICMgdC10ZXN0IGJldHdlZW4gdGhlIHR3byBjb25kaXRpb25zCiAgcHZhbHVlW2ldID0gdCRwLnZhbHVlICMgU3RvcmUgcC12YWx1ZSBudW1iZXIgaSBpbnRvIHRoZSBsaXN0IG9mIHAtdmFsdWVzCn0KaGVhZChwdmFsdWUpCmBgYAoKIyMgU3RhdGlzdGljYWwgU2lnbmlmaWNhbmNlICgqcCotdmFsdWUpIDMvMwpgYGB7cn0KaGlzdCgtbG9nMTAocHZhbHVlKSwgY29sID0gImdyYXkiKSAjIEhpc3RvZ3JhbSBvZiBwLXZhbHVlcyAoLWxvZzEwKQpgYGAKCiMjIFZvbGNhbm8gOiBTdGF0aXN0aWNhbCAmIEJpb2xvZ2ljYWwgMS8zCmBgYHtyfQpwbG90KC1sb2cxMChwdmFsdWUpIH4gZm9sZCkKYGBgCgojIyBWb2xjYW5vIDogU3RhdGlzdGljYWwgJiBCaW9sb2dpY2FsIDIvMwpgYGB7ciBldmFsPUZBTFNFfQpmb2xkX2N1dG9mZiA9IDIKcHZhbHVlX2N1dG9mZiA9IDAuMDEKCnBsb3QoLWxvZzEwKHB2YWx1ZSkgfiBmb2xkKQoKYWJsaW5lKHYgPSBmb2xkX2N1dG9mZiwgY29sID0gImJsdWUiLCBsd2QgPSAzKQphYmxpbmUodiA9IC1mb2xkX2N1dG9mZiwgY29sID0gInJlZCIsIGx3ZCA9IDMpCmFibGluZShoID0gLWxvZzEwKHB2YWx1ZV9jdXRvZmYpLCBjb2wgPSAiZ3JlZW4iLCBsd2QgPSAzKQpgYGAKCiMjIFZvbGNhbm8gOiBTdGF0aXN0aWNhbCAmIEJpb2xvZ2ljYWwgMy8zCmBgYHtyIGVjaG89RkFMU0V9CmZvbGRfY3V0b2ZmID0gMgpwdmFsdWVfY3V0b2ZmID0gMC4wMQoKcGxvdCgtbG9nMTAocHZhbHVlKSB+IGZvbGQpCgphYmxpbmUodiA9IGZvbGRfY3V0b2ZmLCBjb2wgPSAiYmx1ZSIsIGx3ZCA9IDMpCmFibGluZSh2ID0gLWZvbGRfY3V0b2ZmLCBjb2wgPSAicmVkIiwgbHdkID0gMykKYWJsaW5lKGggPSAtbG9nMTAocHZhbHVlX2N1dG9mZiksIGNvbCA9ICJncmVlbiIsIGx3ZCA9IDMpCmBgYAoKCiMjIEZpbHRlcmluZyBmb3IgREVHcyAxLzMKYGBge3J9CmZpbHRlcl9ieV9mb2xkID0gYWJzKGZvbGQpID49IGZvbGRfY3V0b2ZmICMgQmlvbG9naWNhbApzdW0oZmlsdGVyX2J5X2ZvbGQpICMgTnVtYmVyIG9mIGdlbmVzIHN0YWlzZnkgdGhlIGNvbmRpdGlvbgoKZmlsdGVyX2J5X3B2YWx1ZSA9IHB2YWx1ZSA8PSBwdmFsdWVfY3V0b2ZmICMgU3RhdGlzdGljYWwKc3VtKGZpbHRlcl9ieV9wdmFsdWUpCgpmaWx0ZXJfY29tYmluZWQgPSBmaWx0ZXJfYnlfZm9sZCAmIGZpbHRlcl9ieV9wdmFsdWUgIyBDb21iaW5lZApzdW0oZmlsdGVyX2NvbWJpbmVkKQpgYGAKCiMjIEZpbHRlcmluZyBmb3IgREVHcyAyLzMKYGBge3J9CmZpbHRlcmVkID0gZGF0YTJbZmlsdGVyX2NvbWJpbmVkLCBdCmRpbShmaWx0ZXJlZCkKaGVhZChmaWx0ZXJlZCkKYGBgCgojIyBGaWx0ZXJpbmcgZm9yIERFR3MgMy8zCmBgYHtyfQpwbG90KC1sb2cxMChwdmFsdWUpIH4gZm9sZCkKcG9pbnRzKC1sb2cxMChwdmFsdWVbZmlsdGVyX2NvbWJpbmVkXSkgfiBmb2xkW2ZpbHRlcl9jb21iaW5lZF0sCiAgICAgICBjb2wgPSAiZ3JlZW4iKQpgYGAKCiMjIEV4ZXJjaXNlCk9uIHRoZSB2b2xjYW5vICBwbG90LCBoaWdobGlnaHQgdGhlIHVwLXJlZ3VsYXRlZCBnZW5lcyBpbiByZWQgYW5kIHRoZSBkb3dubG9hZC1yZWd1bGF0ZWQgZ2VuZXMgaW4gYmx1ZQoKIyMgU29sdXRpb24gMS8yCgotIFVwLXJlZ3VsYXRlZCBnZW5lcwpgYGB7cn0KIyBTY3JlZW4gZm9yIHRoZSB1cC1yZWd1bGF0ZWQgZ2VuZXMgKCt2ZSBmb2xkKQpmaWx0ZXJfdXAgPSBmaWx0ZXJfY29tYmluZWQgJiBmb2xkID4gMAoKaGVhZChmaWx0ZXJfdXApCmBgYAoKYGBge3J9CiMgTnVtYmVyIG9mIGZpbHRlcmVkIGdlbmVzCnN1bShmaWx0ZXJfdXApCmBgYAoKLSBEb3duLXJlZ3VsYXRlZCBnZW5lcwpgYGB7cn0KIyBTY3JlZW4gZm9yIHRoZSBkb3duLXJlZ3VsYXRlZCBnZW5lcyAoLXZlIGZvbGQpCmZpbHRlcl9kb3duID0gZmlsdGVyX2NvbWJpbmVkICYgZm9sZCA8IDAKCmhlYWQoZmlsdGVyX2Rvd24pCmBgYAoKYGBge3J9CiMgTnVtYmVyIG9mIGZpbHRlcmVkIGdlbmVzCnN1bShmaWx0ZXJfZG93bikKYGBgCgojIyBTb2x1dGlvbiAyLzIKYGBge3J9CnBsb3QoLWxvZzEwKHB2YWx1ZSkgfiBmb2xkKQpwb2ludHMoLWxvZzEwKHB2YWx1ZVtmaWx0ZXJfdXBdKSB+IGZvbGRbZmlsdGVyX3VwXSwgY29sID0gInJlZCIpCnBvaW50cygtbG9nMTAocHZhbHVlW2ZpbHRlcl9kb3duXSkgfiBmb2xkW2ZpbHRlcl9kb3duXSwgY29sID0gImJsdWUiKQpgYGAKCiMjIEhlYXRtYXAgMS81CmBgYHtyfQpoZWF0bWFwKGZpbHRlcmVkKQpgYGAKCiMjIEhlYXRtYXAgMi81Ci0gQnkgZGVmYXVsdCwgYGhlYXRtYXBgIGNsdXN0ZXJzIGdlbmVzIChyb3dzKSBhbmQgc2FtcGxlcyAoY29sdW1ucykgYmFzZWQgb24gW3RoZSBFdWNsaWRlYW4gZGlzdGFuY2VdKGh0dHA6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRXVjbGlkZWFuX2Rpc3RhbmNlKS4KCi0gSW4gdGhlIGNvbnRleHQgb2YgZ2VuZSBleHByZXNzaW9uLCB3ZSBuZWVkIHRvIGNsdXN0ZXIgZ2VuZXMgYW5kIHNhbXBsZXMgYmFzZWQgb24gdGhlIGNvcnJlbGF0aW9uIHRvIGV4cGxvcmUgcGF0dGVybnMgb2YgKipbY28tcmVndWxhdGlvbl0oaHR0cDovL2R4LmRvaS5vcmcvMTAuMTE4Ni8xNDcxLTIxMDUtNS0xOCkqKiAoKipjby1leHByZXNzaW9uKiopIC0gKkd1aWx0IGJ5IEFzc29jaWF0aW9uKi4KCi0gVG8gbGV0IGBoZWF0bWFwYCBjbHVzdGVyIHRoZSBnZW5lcyBhbmQvb3Igc2FtcGxlcywgdGhlIGdlbmVzIGFuZCBzYW1wbGVzIHdpbGwgYmUgY2x1c3RlcmVkIChncm91cGVkKSBieSBjb3JyZWxhdGlvbiBjb2VmZmljaWVudHMgKHVzaW5nIGBjb3JgKSBhbW9uZyB0aGUgZ2VuZXMgYW5kIHNhbXBsZXMuCgojIyBIZWF0bWFwIDMvNQoKYGBge3J9CiMgQ2x1c3RlcmluZyBvZiB0aGUgY29sdW1ucyAoc2FtcGxlcykKY29sX2RlbmRyb2dyYW0gPSBhcy5kZW5kcm9ncmFtKGhjbHVzdChhcy5kaXN0KDEtY29yKGZpbHRlcmVkKSkpKQoKIyBDbHVzdGVyaW5nIG9mIHRoZSByb3dzIChnZW5lcykKcm93X2RlbmRyb2dyYW0gPSBhcy5kZW5kcm9ncmFtKGhjbHVzdChhcy5kaXN0KDEtY29yKHQoZmlsdGVyZWQpKSkpKQpgYGAKCiFbXShpbWFnZXMvZ3VpbHRfYnlfYXNzb2NpYXRpb24uanBnKQoKIyMgSGVhdG1hcCA0LzUKYGBge3J9CiMgSGVhdG1hcCB3aXRoIHRoZSByb3dzIGFuZCBjb2x1bW5zIGNsdXN0ZXJlZCBieSBjb3JyZWxhdGlvbiBjb2VmZmljaWVudHMKaGVhdG1hcChmaWx0ZXJlZCwgUm93dj1yb3dfZGVuZHJvZ3JhbSwgQ29sdj1jb2xfZGVuZHJvZ3JhbSkKYGBgCgojIyBIZWF0bWFwIDUvNQpgYGB7ciBldmFsPUZBTFNFfQpsaWJyYXJ5KGdwbG90cykgIyBMb2FkIHRoZSBncGxvdHMgbGlicmFyeQpoZWF0bWFwKGZpbHRlcmVkLCBSb3d2PXJvd19kZW5kcm9ncmFtLCBDb2x2PWNvbF9kZW5kcm9ncmFtLCBjb2wgPSByZXYocmVkZ3JlZW4oMTAyNCkpKQpgYGAKCmBgYHtyIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoZ3Bsb3RzKSAjIExvYWQgdGhlIGdwbG90cyBsaWJyYXJ5CmhlYXRtYXAoZmlsdGVyZWQsIFJvd3Y9cm93X2RlbmRyb2dyYW0sIENvbHY9Y29sX2RlbmRyb2dyYW0sIGNvbCA9IHJldihyZWRncmVlbigxMDI0KSkpCmBgYAoKIyMgQW5ubm90YXRpb24gMS8zClRvIG9idGFpbiB0aGUgZnVuY3Rpb25hbCBhbm5vdGF0aW9uIG9mIHRoZSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMsIHdlIGFyZSBnb2luZyBmaXJzdCB0byBleHRyYWN0IHRoZWlyIHByb2JlIGlkczoKYGBge3J9CmZpbHRlcmRfaWRzID0gcm93Lm5hbWVzKGZpbHRlcmVkKSAjIGlkcyBvZiB0aGUgZmlsdGVyZWQgREUgZ2VuZXMKbGVuZ3RoKGZpbHRlcmRfaWRzKQpoZWFkKGZpbHRlcmRfaWRzKQpgYGAKCiMjIFNhbml0eSBDaGVjayAoSXJmNikKCiFbRG93biBSZWd1bGF0aW9uIG9mIElyZjZdKGltYWdlcy9pcmY2X2Rvd24ucG5nKQoKYGBge3IgZWNobz1GQUxTRSxmaWcuYWxpZ249ImNlbnRlciIsIGZpZy53aWR0aD00LCBmaWcuaGVpZ2h0PTMuNX0KaXJmNl9pZCA9ICIxNDE4MzAxX2F0IgppcmY2ID0gZmlsdGVyZWRbd2hpY2gocm93bmFtZXMoZmlsdGVyZWQpID09IGlyZjZfaWQpLCBdCmJveHBsb3QoaXJmNlsxOjNdLCBpcmY2WzQ6Nl0sIGNvbCA9IGMoIm5hdnkiLCAib3JhbmdlIiksIG5hbWVzID0gYygiS08iLCAiV1QiKSwgbWFpbiA9ICIiKQpgYGAKCiMjIE11bHRpcGxlIFRlc3RpbmcgQ29ycmVjdGlvbiAxLzMKV2UgY29uZHVjdGVkIGByIG51bWJlcl9vZl9nZW5lc2Agc3RhdGlzdGljYWwgdGVzdHMuIFRoZSBjb21wdXRlZCAqcCotdmFsdWVzIG5lZWQgdG8gYmUgY29ycmVjdGVkIGZvciAqbXVsdGlwbGUgdGVzdGluZyouIFRoZSBjb3JyZWN0aW9uIGNhbiBiZSBwZXJmb3JtZWQgdXNpbmcgYHAuYWRqdXN0YCwgd2hpY2ggc2ltcGx5IHRha2VzIHRoZSBvcmlnbmlhbCAqcCotdmFsdWVzIGEgdmVjdG9yIGFuZCByZXR1cm5zIHRoZSBhZGp1c3RlZCAoY29ycmVjdGVkKSAqcCotdmFsdWVzOgoKYGBge3J9CmFkanVzdGVkLnB2YWx1ZXMgPSBwLmFkanVzdChwdmFsdWUsIG1ldGhvZCA9ICJmZHIiKQpgYGAKCk51bWJlciAqKm9yaWdpbmFsKiogKnAqLXZhbHVlcyAkXGxlcSQgMC4wNSA9IGByIHN1bShwdmFsdWUgPD0gMC4wNSlgIHdoaWxlIHRoZSBudW1iZXIgKiphZGp1c3RlZCoqICgqKmNvcnJlY3RlZCoqKSAqcCotdmFsdWVzIDwgMC4wNSAkXGdlcSQgYHIgc3VtKGFkanVzdGVkLnB2YWx1ZXMgPD0gMC4wNSlgCgojIyBNdWx0aXBsZSBUZXN0aW5nIENvcnJlY3Rpb24gMi8zCkhlcmUgaXMgYW4gZXhhbXBsZSBvZiB0aGUgb3JpZ2luYWwgKnAqLXZhbHVlcyBhbmQgY29ycmVzcG9uZGluZyBhZGp1c3RlZCAqcCotdmFsdWVzOgoKYGBge3IgZWNobz1GQUxTRX0KZGYgPSB0aWJibGUocHZhbHVlID0gcHZhbHVlLCBhZGp1c3RlZC5wdmFsdWUgPSBhZGp1c3RlZC5wdmFsdWVzKQpoZWFkKGRmKQpgYGAKCiMjIE11bHRpcGxlIFRlc3RpbmcgQ29ycmVjdGlvbiAzLzMKYGBge3IgZWNobz1GQUxTRX0KcCA9IGdncGxvdChkZikKcCA9IHAgKyBnZW9tX2hpc3RvZ3JhbShhZXMoeCA9IC1sb2cxMChwdmFsdWUpKSwgYmlucyA9IDEwMDAsIGFscGhhID0gMC41LCBmaWxsID0gInJlZCIpCnAgPSBwICsgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSAtbG9nMTAoYWRqdXN0ZWQucHZhbHVlcykpLCBiaW5zID0gMTAwMCwgYWxwaGEgPSAwLjUsIGZpbGwgPSAiYmx1ZSIpCnByaW50KHApCmBgYAoKIyMgSG9tZXdvcmsKLSBJZGVudGlmeSB0aGUgdG9wIDEwICpiaW9sb2dpY2FsbHkqIHNpZ25pZmljYW50IGdlbmVzIChpLmUuLCBieSBmb2xkLWNoYW5nZSkKLSBJZGVudGlmeSB0aGUgdG9wIDEwICpzdGF0aXN0aWNhbGx5KiBzaWduaWZpY2FudCBnZW5lcyAoaS5lLiwgYnkgKnAqLXZhbHVlKQoKPGNlbnRlcj4KIVtdKGltYWdlcy9qb2tlLnBuZykKPC9jZW50ZXI+Cgo=